package org.rency.common.utils.tool;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.jsoup.Connection;
import org.jsoup.Connection.Method;
import org.jsoup.Connection.Response;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.rency.common.utils.converts.JsonConvert;
import org.rency.common.utils.domain.IPResult;
import org.rency.common.utils.domain.SYSDICT;
import org.rency.common.utils.exception.NotModifiedException;
import org.rency.common.utils.validate.RegValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * HTTP工具类
 * @author: user_rcy@163.com
 * <p>Created on 2015年11月4日</p>
 * @version: V1.0.0
 * @since JDK 1.8
 */
public class HttpUtils {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(HttpUtils.class);
	
	private static final String DEFAULT_QUERY_URL = "http://ip.taobao.com/service/getIpInfo.php";
	
	private HttpUtils(){}
	
	/**
	 * 发送HTTP POST请求
	 * @param uri
	 * @param params
	 * @return
	 * @throws Exception 
	 */
	public static String sendPost(String uri,String params) throws Exception{
		PrintWriter out = null;
		BufferedReader in = null;
		String result = "";
		try{
			URL url = new URL(uri);
			URLConnection connection = url.openConnection();
			connection.setRequestProperty("accept", "*/*");
			connection.setRequestProperty("connection", "Keep-Alive");
			connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
			connection.setDoOutput(true);
			connection.setDoInput(true);
			out = new PrintWriter(connection.getOutputStream());
			out.print(params);
			out.flush();
			in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
			String line = "";
			while( (line = in.readLine() ) != null){
				result += line;
			}
		}catch(Exception e){
			LOGGER.error("send http post request error.uri:{},params:{}.",uri,params,e);
			throw e;
		}finally{
            try{
                if(out!=null){
                    out.close();
                }
                if(in!=null){
                    in.close();
                }
            }catch(IOException ex){
            }
        }
		return result;
	}
	
	/**
	 * JSOUP执行HTTP请求
	 * </br>Created on 2015年11月14日
	 * @param url 请求地址
	 * @param param 如果参数为空则执行get请求,不为空则执行post请求
	 * @return
	 * @throws Exception
	 */
 	public static Response getResponse(String url,Map<String, String> param) throws Exception {
 		try{
 			Connection conn = null;
			if(param == null || param.isEmpty()){
				conn = Jsoup.connect(url).timeout(5000).method(Method.GET);
			}else{
				conn = Jsoup.connect(url).timeout(5000).method(Method.POST).data(param);
			}
			//配置模拟浏览器
			conn.userAgent("Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.102 Safari/537.36");
			Response response = conn.execute();
			//判断服务器返回状态
			if(httpResponseStatus(response.statusCode())){
				return response;
			}else{
				return null;
			}
		}catch(Exception e){
			HttpUtils.parseException(e);
		}
 		return null;
	}

 	/**
 	 * 根据Http相应状态码判断请求返回状态
 	 * @param statusCode
 	 * @return
 	 * @throws Exception
 	 */
	public static boolean httpResponseStatus(int statusCode) throws Exception{
		switch (statusCode) {
			case 100:
				return true;
			case 101:
				return true;
			case 102:
				return true;
			case 200:
				return true;
			case 201:
				return true;
			case 202:
				throw new SocketTimeoutException("The request block wait, and status code["+statusCode+"]");
			case 203:
				return true;
			case 204:
				return true;
			case 205:
				return true;
			case 206:
				return true;
			case 207:
				return true;
			case 300:
				return true;
			case 301:
				return true;
			case 302:
				return true;
			case 303:
				return true;
			case 304:
				throw new NotModifiedException("The request content not modify, and status code["+statusCode+"]");
			case 305:
				return true;
			case 306:
				return true;
			case 307:
				return true;
			case 400:
				throw new RuntimeException("The request cannot be understand on server, and status code["+statusCode+"]");
			case 401:
				throw new RuntimeException("The request should user authorized, and status code["+statusCode+"]");
			case 402:
				return true;
			case 403:
				throw new RuntimeException("status code["+statusCode+"]");
			case 404:
				throw new SocketTimeoutException("The request not found on server, and status code["+statusCode+"]");
			case 405:
				return false;
			case 406:
				return false;
			case 407:
				throw new RuntimeException("The request must use proxy on client, and status code["+statusCode+"]");
			case 408:
				throw new SocketTimeoutException("The request time out, and status code["+statusCode+"]");
			case 409:
				throw new RuntimeException("The request status is conflict, and cannot finish request.And status code["+statusCode+"]");
			case 410:
				throw new RuntimeException("The request is not avaliable, and status code["+statusCode+"]");
			case 411:
				return true;
			case 412:
				return true;
			case 413:
				throw new RuntimeException("The request entity too large, and status code["+statusCode+"]");
			case 414:
				throw new RuntimeException("The request is too long, and status code["+statusCode+"]");
			case 415:
				return true;
			case 416:
				return true;
			case 417:
				return true;
			case 421:
				throw new SocketTimeoutException("The request beyond connect max count, and status code is "+statusCode);
			case 422:
				return true;
			case 423:
				throw new RuntimeException("The request locked, and status code is "+statusCode);
			case 424:
				throw new RuntimeException("The request failed dependency, and status code is "+statusCode);
			case 425:
				return true;
			case 426:
				return true;
			case 449:
				return true;
			case 500:
				throw new SocketTimeoutException("Internal server error, and status code is "+statusCode);
			case 501:
				throw new RuntimeException("Not Implemented, and status code is "+statusCode);
			case 502:
				throw new RuntimeException("Bad Gateway, and status code is "+statusCode);
			case 503:
				throw new SocketTimeoutException("Service Unavailable, and status code is "+statusCode);
			case 504:
				throw new SocketTimeoutException("Gateway timeout, and status code is "+statusCode);
			case 505:
				throw new RuntimeException("Http Version Not Supported, and status code is "+statusCode);
			case 506:
				throw new RuntimeException("Variant Also Negotiates, and status code is "+statusCode);
			case 507:
				throw new RuntimeException("Insufficient Storage, and status code is "+statusCode);
			case 508:
				throw new RuntimeException("Loop Detected, and status code is "+statusCode);
			case 509:
				throw new RuntimeException("Bandwidth Limit Exceeded, and status code is "+statusCode);
			case 510:
				throw new RuntimeException("Not Extended, and status code is "+statusCode);
			case 600:
				return true;
			default:
				return false;
		}
	}

	/**
	 * 根据IP地址判断所属区域
	 * </br>Created on 2015年11月14日
	 * @param queryURL 如果url为空则默认查询淘宝IP地址库
	 * @param ip
	 * @return
	 * @throws Exception
	 */
	public static IPResult getAddrByIP(String queryURL,String ip) throws Exception{
		LOGGER.debug("Query Region With IP Addr:[{}].",ip);
		if(StringUtils.isBlank(ip)){
			LOGGER.error("IP Addr Can Not be Null.[{}]",ip);
			throw new IllegalArgumentException("IP Addr Can Not be Null."+ip);
		}
		if(!RegValidator.isIP(ip)){
			LOGGER.error("IP Addr Format Invalid.[{}]",ip);
			throw new IllegalArgumentException("IP Addr Format Invalid."+ip);
		}
		Map<String, IPResult> cache = new ConcurrentHashMap<String, IPResult>();
		if(cache.containsKey(ip)){
			return cache.get(ip);
		}
		
		if(StringUtils.isBlank(queryURL)){
			queryURL = DEFAULT_QUERY_URL;
		}
		Map<String, String> param = new HashMap<String, String>();
		param.put("ip", ip);
		Response response = getResponse(queryURL,param);
		Document document = response.parse();
		String resp = decodeUnicode(document.body().text());
		if(StringUtils.isBlank(resp)){
			return null;
		}
		@SuppressWarnings("unchecked")
		Map<String, LinkedHashMap<String ,String>> respMap = JsonConvert.json2Object(resp, Map.class);
		LinkedHashMap<String, String> data = respMap.get("data");
		IPResult domain = new IPResult();
		domain.setCountry(data.get("country"));
		domain.setCountryId("country_id");
		domain.setArea(data.get("area"));
		domain.setAreaId(data.get("area_id"));
		domain.setRegion(data.get("region"));
		domain.setRegionId(data.get("region_id"));
		domain.setCity(data.get("city"));
		domain.setCityId(data.get("city_id"));
		domain.setIsp(data.get("isp"));
		domain.setIspId(data.get("isp_id"));
		domain.setIp(data.get("ip"));
		cache.put(ip, domain);
		LOGGER.debug("Query Region With IP Addr:[{}], and Result:[{}].",ip,domain);
		return domain;
	}
	
	/**
	 * 转换为中文
	 * </br>Created on 2015年11月14日
	 * @param theString
	 * @return
	 */
	private static String decodeUnicode(String theString) {
		  char aChar;
		  int len = theString.length();
		  StringBuilder outBuffer = new StringBuilder(len);
		  for (int x = 0; x < len;) {
		   aChar = theString.charAt(x++);
		   if (aChar == '\\') {
		    aChar = theString.charAt(x++);
		    if (aChar == 'u') {
		     int value = 0;
		     for (int i = 0; i < 4; i++) {
		      aChar = theString.charAt(x++);
		      switch (aChar) {
		      case '0':
		      case '1':
		      case '2':
		      case '3':
		      case '4':
		      case '5':
		      case '6':
		      case '7':
		      case '8':
		      case '9':
		       value = (value << 4) + aChar - '0';
		       break;
		      case 'a':
		      case 'b':
		      case 'c':
		      case 'd':
		      case 'e':
		      case 'f':
		       value = (value << 4) + 10 + aChar - 'a';
		       break;
		      case 'A':
		      case 'B':
		      case 'C':
		      case 'D':
		      case 'E':
		      case 'F':
		       value = (value << 4) + 10 + aChar - 'A';
		       break;
		      default:
		       throw new IllegalArgumentException(
		         "Malformed      encoding.");
		      }
		     }
		     outBuffer.append((char) value);
		    } else {
		     if (aChar == 't') {
		      aChar = '\t';
		     } else if (aChar == 'r') {
		      aChar = '\r';
		     } else if (aChar == 'n') {
		      aChar = '\n';
		     } else if (aChar == 'f') {
		      aChar = '\f';
		     }
		     outBuffer.append(aChar);
		    }
		   } else {
		    outBuffer.append(aChar);
		   }
		  }
		  return outBuffer.toString();
		 }
	
	/**
	 * 获取HTTP请求中的参数
	 * </br>Created on 2015年12月29日
	 * @param request
	 * @return
	 */
	public static String buildQueryString(HttpServletRequest request){
		String queryString = request.getQueryString();
		StringBuilder finalQs = new StringBuilder();
		if(StringUtils.isNotBlank(queryString)){
			finalQs.append(queryString);
		}else{
			queryString = createQueryStringFromMap(request.getParameterMap(), "&").toString();
			if(StringUtils.isNotBlank(queryString)){
				finalQs.append(queryString);
			}
		}
		return finalQs.length() == 0 ? null : finalQs.toString();
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private static StringBuilder createQueryStringFromMap(Map m, String ampersand) {
		StringBuilder aReturn = new StringBuilder("");
		Set aEntryS = m.entrySet();
		Iterator aEntryI = aEntryS.iterator();
		while (aEntryI.hasNext()) {
			Map.Entry aEntry = (Map.Entry) aEntryI.next();
			Object value = aEntry.getValue();
			String[] aValues = new String[1];
			if (value == null) {
				aValues[0] = "";
			} else if ((value instanceof List)) {
				List aList = (List) value;
				aValues = (String[]) (String[]) aList.toArray(new String[aList.size()]);
			} else if ((value instanceof String)) {
				aValues[0] = ((String) value);
			} else {
				aValues = (String[]) (String[]) value;
			}
			for (int i = 0; i < aValues.length; i++) {
				append(aEntry.getKey(), aValues[i], aReturn, ampersand);
			}
		}
		return aReturn;
	}

	private static StringBuilder append(Object key, Object value,StringBuilder queryString, String ampersand) {
		if (queryString.length() > 0) {
			queryString.append(ampersand);
		}
		try {
			queryString.append(URLEncoder.encode(key.toString(), SYSDICT.CHARSET));
			queryString.append("=");
			queryString.append(URLEncoder.encode(value.toString(), SYSDICT.CHARSET));
		} catch (UnsupportedEncodingException e) {
		}
		return queryString;
	}
	
	private static void parseException(Exception t) throws Exception{
		if(t instanceof NotModifiedException){
			LOGGER.debug("internet target content not modified."+t);
		}else if(t instanceof UnknownHostException){
			throw new SocketTimeoutException(t.getMessage());
		}else if(t instanceof SocketTimeoutException){
			throw t;
		}else if(t instanceof RuntimeException){
			LOGGER.error("internet target send request error."+t);
		}else if(t instanceof Exception){
			throw t;
		}
	}
}